
unknown 和 any 經常放在一起比較,它們十分相似,但相較於 any, unknown 更為安全,因為它不像 any 一樣任何型別都可以接受,甚至操作不存在的屬性。那究竟在什麼情況下可以使用 unknown 呢?它可以接受什麼型別呢?
本篇將來介紹 unknown 和其使用時機,並會跟 any 一起做對照,在此也把之前介紹 any 的文章放在下方
🔗 文章傳送門:Day15 - any 任意型別
unknown 在 TypeScript 3.0 版本時被引入,被作為 any 更安全的替代,所謂的更安全是指被定義為 unknown 的變數、參數,在進行任何近一步的操作前都會經過型別檢查機制,相反地 any 則不需要
這張圖說明了 TypeScript 中不同型別之間的可指派性(assignability),請先專注看 unknown 和 any
每列(row)表示來源型別,每行(column)表示目標型別。表格的每一格表示是否可以將「來源型別」指派派給「目標型別」
藍色勾勾(✓):來源型別可以被指派給目標型別
綠色勾勾(✓):tsconfig.json 中的 strictNullChecks 關閉時才能被指派
紅色叉叉(✗):來源型別不能指派給目標型別
總結上圖,我們可以得知:
若看不懂沒有關係,透過範例會較好理解
當不確定資料的型別時
當你從外部來源(如 API 呼叫、第三方函式庫等)接收資料時,型別可能是不確定的。這時使用 unknown 可以安全地接收任何型別的資料
作為 any 的替代
unknown 是相較於 any 更安全的替代品。any 雖然很靈活,但它會繞過 TypeScript 的型別系統,可能導致相關的錯誤產生
透過以下幾個範例,來了解 unknown 的特性
unknown 可以接受任何型別的值(any 也是)
let a: any;
let u: unknown;
// ✅ 以下都是 Pass
a = 10;           
a = 'Hello';      
a = true;        
a = { key: 'value' }; 
u = 10;          
u = 'Hello';     
u = true;         
u = { key: 'value' };  
unknown 不能直接對值進行操作
function f1(v1: any) {
  v1.b(); // ✅ Pass
}
function f2(v2: unknown) {
  v2.b();      // ❌ 'a' is of type 'unknown'.
  v2.foo.bar;  // 同上
  v2.trim();   // 同上
  v2();        // 同上
  new v2();    // 同上
  v2[1];       // 同上
}
unknown 能指派給 any 或它自己,但不能直接指派給其他型別,除非進行型別斷言或型別守衛
let value1: unknown;
let value2: any = value1; // ✅ Pass,unknown 可以指派給 any
let value3: unknown = value1; // ✅ Pass,unknown 可以指派給 unknown
let str: string;
str = value1;   // ❌ unknown 不能直接指派給字串,Type 'unknown' is not assignable to type 'string'.
let num: number = value1;   // ❌ unknown 不能直接指派給數字,Type 'unknown' is not assignable to type 'number'.
let booleanVal: boolean = value1; // ❌ ...
let objectVal: object = value1; // ❌ ...
let arr: any[] = value1; // ❌ ...
let f1: Function = value1; // ❌ ...
// 需要進行型別斷言或型別守衛才能賦值給其他型別
if (typeof value1 === 'string') {
  str = value1;     // ✅ Pass,已經檢查過是字串
}
在對 unknown 變數進行任何操作前,需要先透過型別守衛 Type Guards 進行 Narrowing,如使用 typeof、instanceof 、Type Predicate (型別謂詞)等,確定具體型別是什麼,後續的操作才有意義
let value: unknown;
if (typeof value === 'string') {
  console.log(value.toUpperCase()); // ✅ Pass
}
class Person {
  constructor(public name: string) {
    // ... 略 ...
  };
  sayHello() {
    return `Hello ${this.name}`;
  };
}
class Animal {
  constructor(public species: string) {
    // ... 略 ...
  };
  makeSound() {
    return `${this.species} makes a sound`;
  };
}
function greet(value: unknown) {
  if (value instanceof Person) { // ✅ Pass
    console.log(value.sayHello());
  } else if (value instanceof Animal) { // ✅ Pass
    console.log(value.makeSound());
  } else {
    console.log('Unknown type');
  }
}
const john = new Person('John');
const dog = new Animal('Dog');
greet(john);  // Hello John
greet(dog);   // Dog makes a sound
greet(123);   // Unknown type
Type Predicate (型別謂詞) 是屬於自定義型別守衛 (User-Defined Type Guards)的一種parameterName is Type】組成,parameterName 是函式中的參數名稱,Type 就是你定義的型別下方範例中的 animal is Dog 就是 Type Predicate
type Animals = {
  type: string;
};
type Dog = Animals & {
  bark: () => void;
};
function isDog(animal: unknown): animal is Dog {
  return (animal as Animals).type === 'dog';
}
console.log(isDog({ type: 'dog' }));  // true
console.log(isDog({ type: 'human' }));  // false
const unknownVal: unknown = 1;
const numVal: number = unknownVal as number;
console.log(numVal + 1); // ✅ Pass, 2
console.log(unknownVal + 1) // ❌ 'value1' is of type 'unknown'.
| unknown | any | 
|---|---|
| 有型別檢查 | 跳過型別檢查 | 
| 可以接受任何型別 | 可以接受任何型別 | 
| 只能被指派給 unknown 或 any,或需要通過型別斷言後指派給其他型別 | 可以指派給任何型別 | 
| 在未進行型別守衛或型別斷言前,不能對變數進行任何操作 | 可以對變數進行任意操作,沒有限制 | 
💡 若版本允許,使用 unknown 會比 any 更為安全唷
每天的內容有推到 github 上喔